package ghostbanlistd;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;

public class BanConnection extends Thread {
	public static int MAX_PACKET_LENGTH = 65536;
	
	Socket socket;
	DataInputStream in;
	DataOutputStream out;
	
	Server server;
	
	int apiID; //first four bytes of api key that should identify this client
	boolean authenticated;
	boolean canWrite; //whether or not write is enabled for this client
	
	public BanConnection(Socket socket, Server server) throws IOException {
		this.socket = socket;
		this.server = server;
		
		in = new DataInputStream(socket.getInputStream());
		out = new DataOutputStream(socket.getOutputStream());
		
		socket.setSoTimeout(30000);
		
		authenticated = false;
		canWrite = false;
		apiID = -1;
	}
	
	public void println(String message) {
		Main.println("[Connection " + socket.getInetAddress() + "] " + message);
	}
	
	public void run() {
		ByteBuffer buf = ByteBuffer.allocate(MAX_PACKET_LENGTH);
		
		while(true) {
			try {
				int type = in.readShort();
				int length = in.readInt();
				
				if(length > MAX_PACKET_LENGTH) {
					println("Disconnecting: length=" + length + " > " + MAX_PACKET_LENGTH);
					break;
				}
				
				buf.reset();
				in.readFully(buf.array(), 0, length);
				buf.position(0);
				
				if(!authenticated) {
					if(type == 0) {
						if(length == 64) {
							byte[] bytes = new byte[64];
							buf.get(bytes);
							int result = server.database.authenticate(bytes, socket.getInetAddress());
							
							if(result == 0) {
								println("Disconnecting: authentication failed");
								break;
							}
							
							authenticated = true;
							apiID = Util.byteArrayToInt(bytes, 0);
							if(result == 2) canWrite = true;
							
							//write response packet
							out.writeShort(0); //packet type, authentication
							out.writeInt(0); //0 data bytes
						} else {
							println("Disconnecting: invalid api key length, " + length);
							break;
						}
					} else {
						println("Disconnecting: invalid packet type=" + type + " when not authenticated");
						break;
					}
				} else {
					if(type == 1) {
						if(length == 8) {
							long time = buf.getLong();
							buf.reset();
							
							ArrayList<Ban> banList = server.bans.banList(time);
							
							for(Ban ban : banList) {
								boolean result = ban.appendBytes(buf);
								
								if(!result) { //we ran out of bytes in buffer
									//send packet and then reset buffer
									int rlength = buf.position(); //return packet length
									buf.position(0);
									
									out.writeShort(1); //packet type, ban list
									out.writeInt(rlength); //data length
									out.write(buf.array(), 0, rlength);
									
									buf.reset();
								}
							}
							
							//send remaining bans
							int rlength = buf.position(); //return packet length
							if(rlength != 0) {
								buf.position(0);
								
								out.writeShort(1); //packet type, ban list
								out.writeInt(rlength); //data length
								out.write(buf.array(), 0, rlength);
								
								buf.reset();
							}
							
							//send empty packet to notify that there are no more bans
							out.writeShort(1); //packet type, ban list
							out.writeInt(0); //no data bytes
						} else {
							println("Disconnecting: invalid time length, " + length);
							break;
						}
					} if(type == 2 && canWrite) {
						if(length >= 9) {
							byte[] address = new byte[4];
							buf.get(address);
							int reasonCode = buf.getInt();
							String reasonString = Util.getTerminatedString(buf);
							
							Ban ban = new Ban(address, reasonCode, reasonString, apiID);
							server.bans.addBan(ban);
							
							out.writeShort(2); //packet type, ban add
							out.writeInt(0); //0 data bytes
						} else {
							println("Disconnecting: invalid add ban length, " + length);
							break;
						}
					} else {
						println("Disconnecting: invalid packet type=" + type + " when authenticated (write=" + canWrite + ")");
						break;
					}
				}
			} catch(IOException ioe) {
				println("Connection terminated: " + ioe.getLocalizedMessage());
				break;
			}
		}
		
		try {
			socket.close();
		} catch(IOException ioe) {}
		
		server.removeConnection();
	}
}
